﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EnvDTE;
using EnvDTE80;
using msm = MessageSystem.Managed;

namespace MessageBuilderAddin
{
    class MessageBuilder
    {
        private static Dictionary<string, string> ReadFormats = new Dictionary<string, string>()
                                                                {
                                                                    {"System.Byte","{0} = ns.ReadByte();"},
                                                                    {"System.SByte","{0} = ns.ReadSByte();"},
                                                                    {"System.Int16","{0} = ns.ReadInt16();"},
                                                                    {"System.UInt16","{0} = ns.ReadUInt16();"},
                                                                    {"System.Int32","{0} = ns.ReadInt32();"},
                                                                    {"System.UInt32","{0} = ns.ReadUInt32();"},
                                                                    {"System.Int64","{0} = ns.ReadInt64();"},
                                                                    {"System.UInt64","{0} = ns.ReadUInt64();"},
                                                                    {"System.Single","{0} = ns.ReadFloat();"},
                                                                    {"System.String","{0} = ns.ReadString();"}
                                                                };

        private static Dictionary<string, string> WriteFormats = new Dictionary<string, string>()
                                                                {
                                                                    {"System.Byte","ns.WriteByte({0});"},
                                                                    {"System.SByte","ns.WriteSByte({0});"},
                                                                    {"System.Int16","ns.WriteInt16({0});"},
                                                                    {"System.UInt16","ns.WriteUint16({0});"},
                                                                    {"System.Int32","ns.WriteInt32({0});"},
                                                                    {"System.UInt32","ns.WriteUInt32({0});"},
                                                                    {"System.Int64","ns.WriteInt64({0});"},
                                                                    {"System.UInt64","ns.WriteUInt64({0});"},
                                                                    {"System.Single","ns.WriteFloat({0});"},
                                                                    {"System.String","ns.WriteString({0});"}
                                                                };

        private static List<string> Types = new List<string>() { "byte", "sbyte", "short", "ushort", "int", "uint", "float", "string" };

        private static Dictionary<string, string> Type2Type = new Dictionary<string, string>()
                                                                {
                                                                     {"byte","System.Byte"},   
                                                                     {"sbyte","System.SByte"},
                                                                     {"short","System.Int16"},
                                                                     {"ushort","System.UInt16"},
                                                                     {"int","System.Int32"},
                                                                     {"uint","System.UInt32"},
                                                                     {"float","System.Single"},
                                                                     {"string","System.String"},
                                                                };

        public static bool ImplementInterface(CodeClass cClass)
        {
            if(cClass.Attributes.Count==0)
            {
                return false;
            }
            else
            {
                 foreach(var ce in cClass.Attributes)
                 {
                     CodeAttribute ca = ce as CodeAttribute;
                     if (ca.FullName == typeof(msm.MessageAttribute).ToString())
                     {
                        if (cClass.ImplementedInterfaces.Count == 0)
                            cClass.AddImplementedInterface(typeof(msm.IMessage).FullName);
                         return true;
                     }

                     if (ca.FullName == typeof(msm.MessageContentAttribute).ToString())
                     {
                        if (cClass.ImplementedInterfaces.Count == 0)
                            cClass.AddImplementedInterface(typeof(msm.IMessageContent).FullName);
                         return true;
                     }
                 }

                 return false;
            }
        }

        public static void GenerateReadMethod(DTE2 _applicationObject, CodeClass cClass)
        {
            try
            {
                cClass.RemoveMember("Read");
            }
            catch(System.Exception)
            {
            }

            CodeFunction func = cClass.AddFunction("Read", vsCMFunction.vsCMFunctionFunction, vsCMTypeRef.vsCMTypeRefVoid, GetFunctionPosition(cClass));
            func.AddParameter("ns", typeof(msm.NetStream).FullName);
            func.Access = vsCMAccess.vsCMAccessPublic;                   

            TextPoint tp = func.GetStartPoint(vsCMPart.vsCMPartBody);
            TextSelection objSel = _applicationObject.ActiveDocument.Selection as TextSelection;
            objSel.MoveToLineAndOffset(tp.Line, tp.LineCharOffset);
            objSel.SelectLine();
            objSel.Delete();
            objSel.Indent(2);

            int count = GetPropertyCount(cClass);
            foreach (CodeElement m in cClass.Members)
            {
                if (m.Kind == vsCMElement.vsCMElementProperty && !string.Equals(m.Name,"MessageId"))
                {
                    CodeProperty p = (CodeProperty)m;
                    string format;
                    if (ReadFormats.TryGetValue(p.Type.AsFullName, out format))
                    {
                        objSel.Indent(1);
                        objSel.Insert(string.Format(format, p.Name));
                    }
                    else
                    {
                        // 泛型 
                        if (p.Type.AsString.Contains("System.Collections.Generic.List"))
                        {
                            objSel.Indent(1);
                            objSel.Insert(string.Format("{0} = new {1}();", p.Name, p.Type.AsString));

                            objSel.NewLine();
                            objSel.Indent(1);                                                        
                            objSel.Insert(string.Format("int {0}Count = ns.ReadInt32();", p.Name));
                                                        
                            objSel.NewLine();
                            objSel.Indent(1);
                            objSel.Insert(string.Format("for( int i = 0; i < {0}Count; ++i )", p.Name));

                            objSel.NewLine();
                            objSel.Insert("{");

                            objSel.Indent(1);
                            objSel.NewLine();                            
                            string gtype = GetGenericType(p.Type.AsString);
                            if (Types.Contains(gtype))
                            {
                                string ttype = Type2Type[gtype];
                                format = ReadFormats[ttype];
                                objSel.Indent(1);
                                objSel.Insert(string.Format(format, "var v"));
                                objSel.NewLine();
                                objSel.Indent(1);
                                objSel.Insert(string.Format("{0}.Add(v);", p.Name));
                            }
                            else
                            {
                                objSel.Indent(1);
                                objSel.Insert(string.Format("var v = new {0}();", gtype));
                                objSel.NewLine();

                                objSel.Indent(1);
                                objSel.Insert(string.Format("v.Read(ns);", p.Name));

                                objSel.NewLine();
                                objSel.Indent(1);
                                objSel.Insert(string.Format("{0}.Add(v);", p.Name));
                            }

                            objSel.NewLine();
                            objSel.Insert("}");
                        }
                        else
                        {
                            objSel.Indent(1);
                            objSel.Insert(string.Format("{0} = new {1}();", p.Name, p.Type.AsString));
                            objSel.NewLine();
                            objSel.Indent(1);
                            objSel.Insert(string.Format("{0}.Read(ns);", p.Name));
                        }
                    }

                    if (--count>0)
                        objSel.NewLine();
                }
            }

            objSel.NewLine();
            objSel.Insert("");
        }

        public static void GenerateWriteMethod(DTE2 _applicationObject, CodeClass cClass)
        {
            try
            {
                cClass.RemoveMember("Write");
            }
            catch (System.Exception)
            {
            }

            CodeFunction func = cClass.AddFunction("Write", vsCMFunction.vsCMFunctionFunction, vsCMTypeRef.vsCMTypeRefVoid, GetFunctionPosition(cClass));
            func.AddParameter("ns", typeof(msm.NetStream).FullName);
            func.Access = vsCMAccess.vsCMAccessPublic;

            TextPoint tp = func.GetStartPoint(vsCMPart.vsCMPartBody);
            TextSelection objSel = _applicationObject.ActiveDocument.Selection as TextSelection;
            objSel.MoveToLineAndOffset(tp.Line, tp.LineCharOffset);
            objSel.SelectLine();
            objSel.Delete();
            objSel.Indent(2);

            int count = GetPropertyCount(cClass);
            foreach (CodeElement m in cClass.Members)
            {
                if (m.Kind == vsCMElement.vsCMElementProperty && !string.Equals(m.Name, "MessageId"))
                {
                    CodeProperty p = (CodeProperty)m;
                    string format;
                    if (WriteFormats.TryGetValue(p.Type.AsFullName, out format))
                    {
                        objSel.Indent(1);
                        objSel.Insert(string.Format(format, p.Name));
                    }
                    else
                    {
                        // 泛型 
                        if (p.Type.AsString.Contains("System.Collections.Generic.List"))
                        {
                            objSel.Indent(1);
                            objSel.Insert(string.Format("int {0}Count = {1}.Count;", p.Name,p.Name));

                            objSel.NewLine();
                            objSel.Indent(1);
                            objSel.Insert(string.Format("for( int i = 0; i < {0}Count; ++i )", p.Name));

                            objSel.NewLine();
                            objSel.Insert("{");

                            objSel.Indent(1);
                            objSel.NewLine();
                            string gtype = GetGenericType(p.Type.AsString);
                            if (Types.Contains(gtype))
                            {
                                objSel.Indent(1);
                                objSel.Insert(string.Format("var v = {0}[i];", p.Name));
                                string ttype = Type2Type[gtype];
                                format = WriteFormats[ttype];
                                objSel.NewLine();
                                objSel.Indent(1);
                                objSel.Insert(string.Format(format, "v"));
                            }
                            else
                            {
                                objSel.Indent(1);
                                objSel.Insert(string.Format("var v = {0}[i];", p.Name));

                                objSel.NewLine();
                                objSel.Indent(1);
                                objSel.Insert("v.Write(ns);");
                            }

                            objSel.NewLine();
                            objSel.Insert("}");
                        }
                        else
                        {
                            objSel.Indent(1);
                            objSel.Insert(string.Format("{0}.Write(ns);", p.Name));
                        }
                    }

                    if (--count > 0)
                        objSel.NewLine();
                }
            }

            objSel.NewLine();
            objSel.Insert("");
        }

        public static void GenerateMessageId(DTE2 _applicationObject, CodeClass cClass)
        {
            try
            {
                cClass.RemoveMember("MessageId");
            }
            catch (System.Exception)
            {
            }

            CodeAttribute ca = null;
            foreach (var ce in cClass.Attributes)
            {
                ca = ce as CodeAttribute;
                if (ca.FullName == typeof(msm.MessageAttribute).ToString())
                {
                    break;
                }
            }

            if (ca!=null)
            {
                Dictionary<string, string> atts = GetAttributes(ca.Value);
                string sId = "0";
                atts.TryGetValue("Id", out sId);
                GenerateMessageId(_applicationObject, cClass, Convert.ToUInt16(sId));
            }
        }

        private static void GenerateMessageId(DTE2 _applicationObject, CodeClass cClass,UInt16 id)
        {
            TextSelection objSel = _applicationObject.ActiveDocument.Selection as TextSelection;
            CodeElement ce = GetFunctionPosition(cClass) as CodeElement;
            TextPoint tp = ce.EndPoint;
            objSel.MoveToLineAndOffset(tp.Line,tp.LineCharOffset);
            objSel.NewLine();
            objSel.NewLine();
            objSel.Insert(string.Format("public UInt16 MessageId {{ get {{ return {0}; }} }}", id));
        }

        private static object GetFunctionPosition(CodeClass cClass)
        {
            CodeElement result = null;
            foreach (CodeElement m in cClass.Members)
            {
                if (m.Kind == vsCMElement.vsCMElementProperty)
                {
                    result = m;
                }
            }
            return result;
        }

        private static int GetPropertyCount(CodeClass cClass)
        {
            int result = 0;
            foreach (CodeElement m in cClass.Members)
            {
                if (m.Kind == vsCMElement.vsCMElementProperty)
                {
                    ++result;
                }
            }
            return result;
        }

        private static string GetGenericType(string fullName)
        {
            int b = fullName.IndexOf('<');
            int e = fullName.LastIndexOf('>');
            string tp = fullName.Substring(b+1, fullName.Length - b - 2);
            return tp;
        }

        private static Dictionary<string, string> GetAttributes(string value)
        {
            Dictionary<string, string> result = new Dictionary<string,string>();

            var ps = value.Split(',');
            foreach (var p in ps)
            {
                var kvs = p.Split('=');
                int len = kvs.Length;
                for (int i = 0; i < len; i += 2)
                {
                    result.Add(kvs[i], kvs[i + 1]);
                }
            }
            return result;
        }
    }
}
